#ifndef _SOCKET_H_
#define _SOCKET_H_

#include <stdint.h>
#include <tuple>
#include "utils.h"

#ifdef _WIN32
#pragma comment(lib, "Ws2_32.lib")
#include <MSWSock.h>
#include <ws2tcpip.h>
#else

#endif

namespace as
{
namespace snet
{
class EndPoint
{
public:
	EndPoint() = default;
	EndPoint(const char *ip, unsigned short port)
	{
		to_in(ip, port);
	}

	EndPoint& operator=(const EndPoint &o)
	{
		this->to_in(o.m_ip, o.m_port);
		return *this;
	}

	EndPoint& to_in(const char *ip, unsigned short port)
	{
		m_ip = ip;
		m_port = port;
		if (strchr(ip, ':'))
		{
			return to_ipv6(ip, port);
		}
		sa_;
		{
			return to_ipv4(ip, port);
		}
	}

	EndPoint& to_ipv4(const char *ip, unsigned short port)
	{
		if (inet_pton(AF_INET, ip, &this->ipv4_.sin_addr) == 1)
		{
			this->ipv4_.sin_family = AF_INET;
			this->ipv4_.sin_port = htons(port);
			len_ = sizeof(ipv4_);
		}
		return *this;
	}

	EndPoint& to_ipv6(const char *ip, unsigned short port)
	{
		return *this;
	}

	int get_af() const
	{
		if (len_ == sizeof(ipv4_))
			return AF_INET;
		else if (len_ == sizeof(ipv6_))
			return AF_INET6;
		return -1;
	}

	auto to_addr()
	{
		return sa_;
	}

	uint8_t len()
	{
		return len_;
	}

	union {
		sockaddr sa_;
		sockaddr_in ipv4_;
		sockaddr_in6 ipv6_;
	};

	uint8_t len_;

	const char *m_ip = nullptr;
	unsigned short m_port = 0;

};

typedef std::tuple<socket_fd, const char*> socket_result;

class BaseSocket;
typedef std::tuple<BaseSocket*, const char*> accept_result;
class BaseSocket
{
public:
	BaseSocket();
	BaseSocket(socket_fd fd);
	~BaseSocket();

	bool open(int af = AF_INET, int type = SOCK_STREAM, int protocol = 0);
	bool reopen(int af = AF_INET, int type = SOCK_STREAM, int protocol = 0);
	bool isopen() { return isopen(m_fd); }
	static bool isopen(socket_fd fd) { return fd != ERROR_SOCKET_FD; }

	static socket_fd create_socket(int af = AF_INET, int type = SOCK_STREAM, int protocol = 0);
	static BaseSocket* create_socket_obj(int af = AF_INET, int type = SOCK_STREAM, int protocol = 0);

	inline socket_fd get_fd() { return m_fd; }
	inline void set_fd(socket_fd fd) { m_fd = fd; }

	int bind(const char *ip, unsigned short port) const;
	int bind(const EndPoint &ep) const;

	int connect(const char *ip, unsigned short port) const;
	int connect(const EndPoint &ep) const;

	int listen(int backlog = SOMAXCONN);

	accept_result accept();

#ifdef _WIN32
	static bool acceptex(socket_fd listen_fd, socket_fd ac_fd, PVOID lpOutputBuffer, DWORD dwReceiveDataLength, DWORD dwLocalAddressLength, DWORD dwRemoteAddressLength, LPDWORD lpdwBytesReceived, LPOVERLAPPED lpOverlapped);
	static bool connect_ex(socket_fd s, const struct sockaddr* name, int namelen, PVOID lpSendBuffer, DWORD dwSendDataLength, LPDWORD lpdwBytesSent, LPOVERLAPPED lpOverlapped);
#endif

	static void close(socket_fd fd);
	void close();

	int shutdown(int how = SHUT_RDWR) const { return ::shutdown(m_fd, how); }

	static int get_last_err();
	void set_last_err(int error);

	static void set_non_block(socket_fd fd);
	void set_non_block();

	static void set_keep_alive(socket_fd fd);
	void set_keep_alive();

	static int recv(socket_fd fd, void *buf, int len, int flags = 0);
	static int send(socket_fd fd, void *buf, int len, int flags = 0);
protected:
	socket_fd m_fd = ERROR_SOCKET_FD;

	int errno;
#if defined(_WIN32)
	static LPFN_ACCEPTEX __accept_ex;
	static LPFN_GETACCEPTEXSOCKADDRS __get_accept_ex_sockaddrs;
	static LPFN_CONNECTEX __connect_ex;
#endif
};

}

}


#endif